package com.cardmanager.pro.pojo.schedule;

import cn.jh.common.core.base.RandomNumbers;
import com.google.common.collect.Range;
import org.jetbrains.annotations.NotNull;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.util.*;

import static com.google.common.base.Preconditions.checkArgument;
import static java.math.RoundingMode.HALF_UP;


/**
 * {@link RepaymentSchedule} factory or helper class.
 *
 * @author Fuchun
 * @since 1.0
 */
public class RepaymentSchedules {

    private static BigDecimal percent(int from, int to) {
        double v = (Math.random() * (to * 100 - from * 100) + from * 100) / 10000;
        return new BigDecimal(v).setScale(2, HALF_UP);
    }

    static LocalTime[] randomTimes() {
        LocalTime first = randomTime(9);
        int interval = RandomNumbers.inRange(3, 5);
        LocalTime second = randomTime(first.getHour() + interval);
        interval = RandomNumbers.inRange(3, 4);
        LocalTime third = randomTime(second.getHour() + interval);
        return new LocalTime[] { first, second, third };
    }

    private static LocalTime randomTime(int hour) {
        int minutes = RandomNumbers.inRange(1, 59);
        int seconds = RandomNumbers.inRange(1, 59);
        return LocalTime.of(hour, minutes, seconds);
    }

    /**
     * 生成一个随机百分比，范围：[0.40, 0.60]
     */
    static BigDecimal pct45To55() {
        return percent(45, 55);
    }

    static BigDecimal pct2To6() {
        return percent(2, 6);
    }

    // 随机拆分给定的 Decimal
    static BigDecimal[] randomSplit(@NotNull BigDecimal value) {
        BigDecimal pct = pct45To55();
        BigDecimal first = value.multiply(pct).setScale(2, HALF_UP);
        BigDecimal second = value.subtract(first).setScale(2, HALF_UP);
        return new BigDecimal[] { first, second, value };
    }

    private final LocalDate thisDay = LocalDate.now();

    private final Long userId;
    /** 用户要还款总额。*/
    private BigDecimal totalAmount;
    /** 单日最大限额。*/
    private BigDecimal oneDayMaxQuota = new BigDecimal("3500");

    /** 单日最大限额。*/
    private BigDecimal onePayMaxQuota = new BigDecimal("2000");

    /** 单笔最大限额。*/
    private BigDecimal oneDayMax = new BigDecimal("2000");
    /** 单笔最小限额。*/
    private BigDecimal oneDayMin = new BigDecimal("200");

    /** 单笔消费手续费。*/
    private BigDecimal singleCc;
    /** 单笔交易费率。*/
    private BigDecimal singlePmtRate;
    /** 单笔代还手续费。*/
    private BigDecimal singleRc = new BigDecimal("1");
    /** 单笔代还费率。*/
    private BigDecimal singleRpyRate = new BigDecimal("0.00");

    /** 帐单日。*/
    private LocalDate billDate;

    /** 还款日。*/
    private LocalDate repaymentDate;

    /** 开始执行计划的日期。*/
    private LocalDate executeDate;

    private transient Range<LocalDate> duration;
    private transient Period billPeriod;

    public RepaymentSchedules(Long userId) {
        this.userId = userId;
    }

    /**
     * 设置用户总还款额。
     *
     * @param totalAmount 用户总还款额。
     */
    public void setTotalAmount(BigDecimal totalAmount) {
        this.totalAmount = totalAmount;
    }

    /**
     * 设置每天最大限额。
     *
     * @param oneDayMaxQuota 每天最大限额。
     */
    public void setOneDayMaxQuota(BigDecimal oneDayMaxQuota) {
        this.oneDayMaxQuota = oneDayMaxQuota;
    }

    public BigDecimal getOneDayMax() {
        return oneDayMax;
    }

    public void setOneDayMax(BigDecimal oneDayMax) {
        this.oneDayMax = oneDayMax;
    }

    public BigDecimal getOneDayMin() {
        return oneDayMin;
    }

    public void setOneDayMin(BigDecimal oneDayMin) {
        this.oneDayMin = oneDayMin;
    }

    /**
     * 设置单笔交易手续费。
     *
     * @param singleCc 单笔交易手续费。
     */
    public void setSingleCc(BigDecimal singleCc) {
        this.singleCc = singleCc;
    }

    /**
     * 设置单笔交易费率。
     *
     * @param singlePmtRate 单笔交易费率。
     */
    public void setSinglePmtRate(BigDecimal singlePmtRate) {
        this.singlePmtRate = singlePmtRate;
    }

    /**
     * 设置单笔代还手续费。
     *
     * @param singleRc 单笔代还手续费。
     */
    public void setSingleRc(BigDecimal singleRc) {
        this.singleRc = singleRc;
    }

    /**
     * 设置单笔代还交易费率。
     *
     * @param singleRpyRate 单笔代还交易费率。
     */
    public void setSingleRpyRate(BigDecimal singleRpyRate) {
        this.singleRpyRate = singleRpyRate;
    }

    public void setBillDate(LocalDate billDate) {
        this.billDate = billDate;
    }

    public void setRepaymentDate(LocalDate repaymentDate) {
        this.repaymentDate = repaymentDate;
    }

    /**
     * 设置开始执行计划的日期。如果没有设置该值，则默认自动从第2天开始算起。
     *
     * @param executeDate 开始执行计划的日期。
     */
    public void setExecuteDate(LocalDate executeDate) {
        this.executeDate = executeDate;
    }

    BigDecimal getSingleCc() {
        return singleCc;
    }

    BigDecimal getSinglePmtRate() {
        return singlePmtRate;
    }

    BigDecimal getSingleRc() {
        return singleRc;
    }

    BigDecimal getSingleRpyRate() {
        return singleRpyRate;
    }

    private void checkDuration() {
        if (duration != null) return;
        LocalDate std = executeDate != null ? executeDate : thisDay;
        checkArgument(billDate != null, "The billDate must be not null.");
        checkArgument(repaymentDate != null, "The repaymentDate must be not null.");
        checkArgument(std.isBefore(repaymentDate), "The repaymentDate is expired.");
        checkArgument(repaymentDate.isAfter(billDate), "The repaymentDate must be after the billDate.");
        LocalDate from, to = repaymentDate;
        if (std.isBefore(billDate)) {
            from = billDate;
        } else {
            from = std;
        }
        duration = Range.closed(from, to);
        billPeriod = from.until(to);
    }

    private void checkRateAndCc() {
        checkArgument(singleCc != null && singleRc != null,
                "The singleCc and singleRc must be not null");
        checkArgument(singlePmtRate != null && singleRpyRate != null,
                "The singlePmtRate and singleRpyRate must be not null");

    }

    public RepaymentSchedule schedule() {
        checkDuration();
        checkRateAndCc();
        BigDecimal remainder = totalAmount;
        BigDecimal remainSum = BigDecimal.ZERO;
        LocalDate date = duration.lowerEndpoint();

        RepaymentSchedule.Builder rb = new RepaymentSchedule.Builder()
                .setUserId(userId)
                .setPeriod(billPeriod)
                .setTotalAmount(totalAmount);

        boolean isRemain = true;
        do {
            // amount 当天的交易和还款额
            BigDecimal amount, remainPart;
            BigDecimal remainPct = pct2To6();
            if (remainder.compareTo(oneDayMaxQuota) > 0) {
                remainPart = oneDayMaxQuota.multiply(remainPct);
                remainSum = remainSum.add(remainPart);
                amount = oneDayMaxQuota.subtract(remainPart);
            } else {
                amount = remainder;
                BigDecimal tryAmount = amount.add(remainSum);
                if (tryAmount.compareTo(onePayMaxQuota) < 0) {
                    amount = tryAmount;
                    isRemain = false;
                }
            }

            ScheduleItem.Builder sib = new ScheduleItem.Builder()
                    .setDate(date);
            addItemParts(sib, amount);

            rb.addSchedules(sib.build());

            remainder = remainder.subtract(amount);
            date = date.plusDays(1);
        } while (remainder.compareTo(BigDecimal.ZERO) > 0);

        if (isRemain)
            addRemain(rb, date, remainSum);

        return rb.build();
    }

    private void addRemain(RepaymentSchedule.Builder rb, LocalDate date, BigDecimal remainSum) {
        if (remainSum.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        ScheduleItem.Builder sib = new ScheduleItem.Builder()
                .setDate(date);
        addItemParts(sib, remainSum);
        rb.addSchedules(sib.build());
    }

    private void addItemParts(ScheduleItem.Builder sib, BigDecimal amount) {
        BigDecimal[] amountParts;
        LocalTime[] times = randomTimes();
        if (amount.compareTo(onePayMaxQuota) < 0) {
            amountParts = new BigDecimal[] { amount, amount };
            times = new LocalTime[] { times[0], times[2] };
        } else {
            amountParts = randomSplit(amount);
        }
        int i = 0;
        ItemAction action = ItemAction.CONSUMPTION;
        // 消费手续费, 交易费
        BigDecimal commission, tradingFee;
        for (LocalTime time : times) {

            BigDecimal amountPart = amountParts[i];
            if (i == times.length - 1) {
                action = ItemAction.REPAYMENT;
                commission = singleRc;
                tradingFee = amountPart.multiply(singleRpyRate).setScale(2, HALF_UP);
            } else {
                commission = singleCc;
                tradingFee = amountPart.multiply(singlePmtRate).setScale(2, HALF_UP);
            }
            ItemPart itemPart = new ItemPart.Builder()
                    .setDateTime(sib.getDate().atTime(time))
                    .setAmount(amountPart)
                    .setCommission(commission)
                    .setTradingFee(tradingFee)
                    .setAction(action).build();
            sib.addItemParts(itemPart);
            i++;
        }
    }


}
